<?php

class FormElement {
    
    protected $properties;
    protected $required_mark;

    public function getProperties($name = null) {
        if(!is_null($name)) {
            if(isset($this->properties[$name])) {
                return $this->properties[$name];
            }
            return null;
        }
        return $this->properties;
    }

    public function getAttributes() {
        static $nonAttributes = array('label', 'text', 'required');

        $toKeys = array_fill_keys($nonAttributes, 'foo');
        return array_diff_key($this->getProperties(), $toKeys);
    }

    public function setProperty($name, $value) {
        if( ($name != 'type') && ($name != 'id') ) {
            $this->attributes[$name] = $value;
            return true;
        }
        return false;
    }

    public function setRequiredMark($mark) {
        $this->required_mark = $mark;
    }


    // Must be overloaded
    protected function write($xmlDoc) {
    }

}

class Input extends FormElement {

    public function __construct($type, $id, $label = null, $value = null,
            $extraAttrs = null, $required = false) {
        $this->properties = array('type' => $type, 'id' => $id, 'name' => $id);
        if(!is_null($label)) {
            $this->properties['label'] = $label;
        }
        if(!is_null($value)) {
            $this->properties['value'] = $value;
        }
        if(!is_null($extraAttrs) && is_array($extraAttrs)) {
            $this->properties = array_merge($this->properties, $extraAttrs);
        }
        $this->properties['required'] = $required;
    }

    public function getType() {
        return $this->getProperties('type');
    }

    public function getId() {
        return $this->getProperties('id');
    }

    public function getName() {
        return $this->getId();
    }

    public function getLabel() {
        return $this->getProperties('label');
    }

    public function getValue() {
        return $this->getProperties('value');
    }

    public function setLabel($label) {
        $this->setProperty('label', $label);
    }

    public function setValue($value) {
        $this->setProperty('value', $value);
    }

    public function write($xmlDoc) {
        if($label = $this->getLabel()) {
            $xmlDoc->element('label', array('for' => $this->getId()), $label);
            if($this->getProperties('required')) {
                $xmlDoc->element('span', array('class' => 'required_field_mark'),
                        $this->required_mark);
            }
        }
        $xmlDoc->element('input', $this->getAttributes());
    }

}

class InputText extends Input {

    public function __construct($id, $label = null, $value = null,
            $extraAttrs = null, $required = true) {
        parent::__construct('text', $id, $label, $value, $extraAttrs, $required);
    }

}

class InputPassword extends Input {

    public function __construct($id, $label = null, $value = null,
            $extraAttrs = null, $required = true) {
        parent::__construct('password', $id, $label, $value, $extraAttrs,
                $required);
    }

}

class InputHidden extends Input {

    public function __construct($id, $value, $extraAttrs = null) {
        if(is_array($extraAttrs)) {
            if(isset($extraAttrs['class'])) {
                $extraAttrs['class'] .= ' hidden';
            }
            else {
                $extraAtrrs['class'] = 'hidden';
            }
        }
        else {
            $extraAttrs = array('class' => 'hidden');
        }
        parent::__construct('hidden', $id, null, $value, $extraAttrs);
    }

}

class InputSubmit extends Input {

    public function __construct($id, $value = null, $extraAttrs = null) {
        if(is_array($extraAttrs)) {
            if(isset($extraAttrs['class'])) {
                $extraAttrs['class'] .= ' button';
            }
            else {
                $extraAtrrs['class'] = 'button';
            }
        }
        else {
            $extraAttrs = array('class' => 'button');
        }
        parent::__construct('submit', $id, null, $value, $extraAttrs);
    }
}

class Button extends FormElement {

    public function __construct($id, $value = null, $type = 'button',
            $extraAttrs = null) {

        if(is_array($extraAttrs)) {
            if(isset($extraAttrs['class'])) {
                $extraAttrs['class'] .= ' button';
            }
            else {
                $extraAtrrs['class'] = 'button';
            }
        }
        else {
            $extraAttrs = array('class' => 'button');
        }

        $this->properties = array('id' => $id, 'name' => $id, 'type' => $type);
        if(!is_null($value)) {
            $this->properties['value'] = $value;
        }
        if(!is_null($extraAttrs) && is_array($extraAttrs)) {
            $this->properties = array_merge($this->properties, $extraAttrs);
        }
    }

    public function getValue() {
        return $this->getProperties('value');
    }

    public function write($xmlDoc) {
        $xmlDoc->element('button', $this->getProperties(), $this->getValue());
    }
}

class TextArea extends FormElement {

    public function __construct($id, $cols = 50, $rows = 5, $label = null,
            $text = null, $extraAttrs = null, $required = true) {
        $this->properties = array('id' => $id, 'name' => $id, 'cols' => $cols,
                'rows' => $rows);
        if(!is_null($label)) {
            $this->properties['label'] = $label;
        }
        if(!is_null($text)) {
            $this->properties['text'] = $text;
        }
        if(!is_null($extraAttrs) && is_array($extraAttrs)) {
            $this->properties = array_merge($this->properties, $extraAttrs);
        }
        $this->properties['required'] = $required;
    }

    public function getId() {
        return $this->getProperties('id');
    }

    public function getName() {
        return $this->getId();
    }

    public function getSize() {
        return array('cols' => $this->getProperties('cols'),
                'rows' => $this->getProperties('rows'));
    }

    public function getLabel() {
        return $this->getProperties('label');
    }

    public function getText() {
        return $this->getProperties('text');
    }

    public function setSize($rows = null, $cols = null) {
        if(is_int($rows)) {
            $this->setProperty('rows', $rows);
        }
        if(is_int($cols)) {
            $this->setProperty('cols', $cols);
        }
    }

    public function setLabel($label) {
        $this->setProperty('label', $label);
    }

    public function setText($text) {
        $this->setProperty('value', $text);
    }

    public function write($xmlDoc) {
        if($label = $this->getLabel()) {
            $xmlDoc->element('label', array('for' => $this->getId()), $label);
            if($this->getProperties('required') === true) {
                $xmlDoc->element('span', array('class' => 'required_field_mark'),
                        $this->required_mark);
            }
        }
        $xmlDoc->elementStart('textarea', $this->getAttributes());
        if($text = $this->getText()) {
            $xmlDoc->text($text);
        }
        $xmlDoc->elementEnd('textarea');
    }

}

class Radio extends FormElement {

    private $name;
    private $options;
    private $checked;

    public function __construct($name, $label = null, $checked = null,
            $extraAttrs = null, $required = true) {
        $this->name = $name;
        $this->options = array();
        $this->checked = $checked;
        if(!is_null($label)) {
            $this->properties['label'] = $label;
        }
        if(!is_null($extraAttrs) && is_array($extraAttrs)) {
            $this->properties = array_merge($this->properties, $extraAttrs);
        }
        $this->properties['required'] = $required;
    }

    public function getLabel() {
        return $this->getProperties('label');
    }

    public function setLabel($label) {
        $this->setProperty('label', $label);
    }

    public function addOption($value, $label) {
        $this->options[$value] = $label;
    }

    public function write($xmlDoc) {
        if($label = $this->getLabel()) {
            $xmlDoc->element('span', null, $label);
            if($this->getProperties('required')) {
                $xmlDoc->element('span', array('class' => 'required_field_mark'),
                        $this->required_mark);
            }
        }
        if($this->options && is_array($this->options)) {
            foreach($this->options as $value => $label) {
                $attrs = array('type' => 'radio', 'name' => $this->name,
                        'id' => $this->name . $value, 'value' => $value,
                        'class' => 'radio');
                if($value == $this->checked) {
                    $attrs['checked'] = 'checked';
                }
                $xmlDoc->element('input', $attrs);
                $xmlDoc->element('label', array('for' => $this->name . $value),
                        $label);
            }
        }
    }

}

class Select extends FormElement {
    
    private $options;
    private $selected;

    public function __construct($id, $label = null, $selected = null,
            $extraAttrs = null, $required = true) {
        $this->options = array();
        $this->selected = $selected;
        $this->properties = array('id' => $id, 'name' => $id);
        if(!is_null($label)) {
            $this->properties['label'] = $label;
        }
        if(!is_null($extraAttrs) && is_array($extraAttrs)) {
            $this->properties = array_merge($this->properties, $extraAttrs);
        }
        $this->properties['required'] = $required;
    }

    public function getId() {
        return $this->getProperties('id');
    }

    public function getName() {
        return $this->getId();
    }

    public function getLabel() {
        return $this->getProperties('label');
    }

    public function setLabel($label) {
        $this->setProperty('label', $label);
    }

    public function addOption($value, $text) {
        $this->options[$value] = $text;
    }

    public function write($xmlDoc) {
        if($label = $this->getLabel()) {
            $xmlDoc->element('label', array('for' => $this->getId()), $label);
            if($this->getProperties('required')) {
                $xmlDoc->element('span', array('class' => 'required_field_mark'),
                        $this->required_mark);
            }
        }
        $xmlDoc->elementStart('select', $this->getAttributes());
        if($this->options && is_array($this->options)) {
            foreach($this->options as $value => $text) {
                $attrs = array('value' => $value);
                if($value == $this->selected) {
                    $attrs['selected'] = 'selected';
                }
                $xmlDoc->element('option', $attrs, $text);
            }
        }
        $xmlDoc->elementEnd('select');
    }

}

class Span {

    private $text;
    private $attributes;

    public function __construct($text, $class = null, $extraAttrs = null) {
        $this->text = $text;
        $this->attributes = array();
        if($class) {
            $this->attributes['class'] = $class;
        }
        if($extraAttrs && is_array($extraAttrs)) {
            $this->attributes = array_merge($this->attributes, $extraAttrs);
        }
    }

    public function write($xmlDoc) {
        $xmlDoc->element('span', $this->attributes, $this->text);
    }

}

class Form {

    private $xmlDoc;
    private $action;
    private $method;
    private $extraAttrs;
    private $elements;
    private $fieldsetsStart;
    private $fieldsetsEnd;
    private $required_mark;

    public function __construct($xmlDoc, $action, $method, $extraAttrs = null) {
        $this->xmlDoc = $xmlDoc;
        $this->action = $action;
        $this->method = $method;
        $this->extraAttrs = $extraAttrs;
        $this->elements = array();
        $this->fieldsetsStart = array();
        $this->fieldsetsEnd = array();
        $this->required_mark = '*';
    }

    public function setRequiredMark($mark = '*') {
        $this->required_mark = $mark;
    }

    public function addElement($element) {
        $this->elements[] = $element;
    }

    public function addInputText($id, $label = null, $value = null,
            $extraAttrs = null, $required = true) {
        $this->elements[] = new InputText($id, $label, $value, $extraAttrs,
                $required);
    }

    public function addInputPassword($id, $label = null, $value = null,
            $extraAttrs = null, $required = true) {
        $this->elements[] = new InputPassword($id, $label, $value, $extraAttrs,
                $required);
    }

    public function addInputHidden($id, $value, $extraAttrs = null) {
        $this->elements[] = new InputHidden($id, $value, $extraAttrs);
    }

    public function addInputSubmit($id, $value = null, $extraAttrs = null) {
        $this->elements[] = new InputSubmit($id, $value, $extraAttrs);
    }

    public function addButton($id, $value = null, $type = 'button',
            $extraAttrs = null) {
        $this->elements[] = new Button($id, $value, $type, $extraAttrs);
    }

    public function addTextArea($id, $cols = 50, $rows = 5, $label = null,
            $text = null, $extraAttrs = null, $required = true) {
        $this->elements[] = new TextArea($id, $cols, $rows, $label, $text,
                $extraAttrs, $required);
    }

    public function addSpan($text, $class = null, $extraAttrs = null) {
        $this->elements[] = new Span($text, $class, $extraAttrs);
    }

    public function startFieldset($id, $legend = null, $class = null,
            $extraAttrs = null) {
        $attrs = array('id' => $id);
        if($class) {
            $attrs['class'] = $class;
        }
        if($legend) {
            $attrs['legend'] = $legend;
        }
        if($extraAttrs && is_array($extraAttrs)) {
            $attrs = array_merge($attrs, $extraAttrs);
        }
        $this->fieldsetsStart[count($this->elements)] = $attrs;
    }

    public function endFieldset() {
        $this->fieldsetsEnd[count($this->elements)] = 'END';
    }

    public function flush() {
        $attrs = array('action' => $this->action, 'method' => $this->method);
        if ( $this->extraAttrs && is_array($this->extraAttrs) ) {
            $attrs = array_merge($attrs, $this->extraAttrs);
        }
        $this->xmlDoc->elementStart('form', $attrs);
        if(is_array($this->elements)) {
            $i = 0;
            foreach($this->elements as $element) {

                if(isset($this->fieldsetsEnd[$i])) {
                    $this->xmlDoc->elementEnd('fieldset');
                }

                if(isset($this->fieldsetsStart[$i])) {
                    if(isset($this->fieldsetsStart[$i]['legend'])) {
                        $legend = $this->fieldsetsStart[$i]['legend'];
                        $this->fieldsetsStart[$i] =
                            array_diff_key($this->fieldsetsStart[$i],
                                    array('legend' => 'legend'));
                    }
                    $this->xmlDoc->elementStart('fieldset',
                            $this->fieldsetsStart[$i]);
                    if(isset($legend)) {
                        $this->xmlDoc->element('legend', null, $legend);
                    }
                }

                $element->setRequiredMark($this->required_mark);
                $element->write($this->xmlDoc);
                $i++;
                
            }
        }
        $this->xmlDoc->elementEnd('form');
    }
}
